#include <drivers/gpio.h>
#include <drivers/platform.h>
#include <list.h>
#include <types.h>
#include <string.h>
#include <stm32f1.h>
#include <irqnum.h>
#include <drivers/irq.h>
#include <config.h>
#include <io.h>


#define GPIO_CLOCK_BASE          (0x40021018)

#define GPIO_REG_SIZE            (0x400)
#define GPIOX_BASE(x)            ((APB2PERIPH_BASE + 0x0800) + GPIO_REG_SIZE * (x))

#define GPIO_DATA_REG(x)         (GPIOX_BASE(x) + 0x0C)
#define GPIO_BSRR_REG(x)         (GPIOX_BASE(x) + 0x10)

#define EXTI_IMR_REG             (EXTI_BASE)
#define EXTI_EMR_REG             (EXTI_BASE + 0x04)
#define EXTI_RTSR_REG            (EXTI_BASE + 0x08)
#define EXTI_FTSR_REG            (EXTI_BASE + 0x0C)

#define EXTI_AFIO_CR_REG(x)      (AFIO_BASE + 0x08 + (x)*0x04)


static void stm32f1_set_clock(int16_t pin, int status)
{
    size_t clock_val = read32(GPIO_CLOCK_BASE);

    if(status) {
        clock_val |= (1 << IO_TO_PORT(pin) + 2);
    }
    else {
        clock_val &= ~(1 << IO_TO_PORT(pin) + 2);
    }

    write32(GPIO_CLOCK_BASE, clock_val);
}


/**
 * MUX: |   High 8bit   |    low 8bit   |
 * FUN: |   pin mode    |      FUNC     |
 */
static void stm32f1_set_mux(int16_t pin, uint16_t mux)
{
    uint32_t port = IO_TO_PORT(pin);
    uint32_t mode_bit = (IO_TO_PIN(pin) * 4) & 0x1F;
    uint32_t pin_bit = IO_TO_PIN(pin) >> 3;
    size_t moder_val = read32(GPIOX_BASE(port) + pin_bit * 0x04);

    mux >>= 8;

    moder_val &= ~( 0xF << mode_bit);
    moder_val |= ( mux << mode_bit);

    write32(GPIOX_BASE(port) + pin_bit * 0x04, moder_val);
}


static void stm32f1_set_dir(int16_t pin, uint8_t flag)
{
    uint32_t port = IO_TO_PORT(pin);
    uint32_t mode_bit = (IO_TO_PIN(pin) * 4) & 0x1F;
    uint32_t pin_bit = IO_TO_PIN(pin) / 8;
    size_t moder_val = read32(GPIOX_BASE(port) + pin_bit * 0x04);

    moder_val &= (~(0xF << mode_bit));
    if(flag) {
        moder_val |= (3 << mode_bit);
    }

    write32(GPIOX_BASE(port) + pin_bit * 0x04, moder_val);
}


static void stm32f1_set_drv(int16_t pin, uint8_t drv)
{
    GPIO_SYSLOG_WARN("%d, %d: this operation is not supported", pin, drv);
}


static void stm32f1_set_pulldown(int16_t pin, uint8_t flag)
{
    uint32_t port = IO_TO_PORT(pin);
    uint32_t mode_bit = (IO_TO_PIN(pin) * 4) & 0x1F;
    uint32_t pin_bit = IO_TO_PIN(pin) / 8;
    size_t moder_val = read32(GPIOX_BASE(port) + pin_bit * 0x04);
    size_t brss = read32(GPIO_BSRR_REG(port));

    moder_val &= ~(0xF << mode_bit);
    moder_val |= (0x3 << mode_bit);

    if(flag) {
        brss |= BIT(IO_TO_PIN(pin) + 16);
    }

    write32(GPIOX_BASE(port) + pin_bit * 0x04, moder_val);
    write32(GPIO_BSRR_REG(port), brss);
}


static void stm32f1_set_pullup(int16_t pin, uint8_t flag)
{
    uint32_t port = IO_TO_PORT(pin);
    uint32_t mode_bit = (IO_TO_PIN(pin) * 4) & 0x1F;
    uint32_t pin_bit = IO_TO_PIN(pin) / 8;
    size_t moder_val = read32(GPIOX_BASE(port) + pin_bit * 0x04);
    size_t brss = read32(GPIO_BSRR_REG(port));

    moder_val &= ~(0xF << mode_bit);
    moder_val |= (0x3 << mode_bit);

    if(flag) {
        brss |= BIT(IO_TO_PIN(pin));
    }

    write32(GPIOX_BASE(port) + pin_bit * 0x04, moder_val);
    write32(GPIO_BSRR_REG(port), brss);
}


static void stm32f1_set_value(int16_t pin, uint8_t value)
{
    uint32_t port = IO_TO_PORT(pin);
    size_t brss_val = read32(GPIO_BSRR_REG(port));

    if(value) {
        brss_val |= BIT(IO_TO_PIN(pin));
    }
    else {
        brss_val |= BIT((1 << (IO_TO_PIN(pin) + 16)));
    }

    write32(GPIO_BSRR_REG(port), brss_val);
}


static int stm32f1_get_value(int16_t pin)
{
    uint32_t port = IO_TO_PORT(pin);
    uint32_t num = IO_TO_PIN(pin);

    return (read32(GPIO_DATA_REG(port)) & BIT(num)) ? true : false;
}


static int stm32f1_set_exti(int16_t pin, int flag)
{
    int gpio_pin = IO_TO_PIN(pin);
    uint32_t gpio_port = IO_TO_PORT(pin);

    write32(EXTI_IMR_REG, read32(EXTI_IMR_REG) | BIT(gpio_pin));

    if(flag & IRQF_RISING || flag & IRQF_HIGH) {
        write32(EXTI_RTSR_REG, read32(EXTI_RTSR_REG) | BIT(gpio_pin));
    }

    if(flag & IRQF_FALLING || flag & IRQF_LOW) {
        write32(EXTI_FTSR_REG, read32(EXTI_FTSR_REG) | BIT(gpio_pin));
    }

    //set the mode to reset status
    stm32f1_set_mux(pin, 0x4);

    //set gpio exti map port 
    uint32_t afiocr_val = read32(EXTI_AFIO_CR_REG(gpio_pin >> 2));
    afiocr_val &= (~(0xF << ((gpio_pin & 3) * 4)));
    afiocr_val |= (gpio_port << ((gpio_pin & 3) * 4));
    write32(EXTI_AFIO_CR_REG(gpio_pin >> 2), afiocr_val);

    switch(gpio_pin) {
        case 0:  return EXTI0_IRQNUM;
        case 1: return EXTI1_IRQNUM;
        case 2: return EXTI2_IRQNUM;
        case 3: return EXTI3_IRQNUM;
        case 4: return EXTI4_IRQNUM;
        case 5 ... 9: return EXTI9_5_IRQNUM;
        case 10 ... 15: return EXTI15_10_IRQNUM;
        default: return -1;
    }

    return -1;
}


static struct gpio_ops stm32f1_ops = {
    .init = NULL,
    .set_clock = stm32f1_set_clock,
    .set_mux = stm32f1_set_mux,
    .set_dir = stm32f1_set_dir,
    .set_drv = stm32f1_set_drv,
    .set_pulldown = stm32f1_set_pulldown,
    .set_pullup = stm32f1_set_pullup,
    .set_value = stm32f1_set_value,
    .get_value = stm32f1_get_value,
    .set_exti = stm32f1_set_exti,
};


static struct gpio_desc stm32f1_gpio = {
    .name = "stm32f1-gpio",
    .npins = CONFIG_GPIO_PINS,
    .ops = &stm32f1_ops,
};


static int stm32f1_gpio_register(void)
{
    gpio_desc_register(&stm32f1_gpio);
    return 0;
}


base_initcall(stm32f1_gpio_register);
